package com.haha.lock;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 基于Redisson的分布式锁具体实现类
 *
 * <p>该类实现了{@link AbstractDistributedLock}抽象类，使用Redisson客户端提供分布式锁功能，
 * 支持可重入锁、尝试加锁、阻塞加锁等特性，适用于分布式环境下的资源同步控制。</p>
 *
 * <p><b>主要特性：</b></p>
 * <ul>
 *   <li>支持可重入锁，同一线程可多次获取同一把锁</li>
 *   <li>提供尝试加锁和阻塞加锁两种模式</li>
 *   <li>自动处理锁的获取和释放，确保线程安全</li>
 *   <li>支持锁的持有状态检查</li>
 * </ul>
 *
 * @author yang
 * @version 1.0
 * @since 2025-10-08
 * @see AbstractDistributedLock
 * @see RedissonClient
 * @see RLock
 */

public class RedissonDistributedLock extends AbstractDistributedLock {

    /** Redisson客户端实例，用于操作Redis分布式锁 */
    private final RedissonClient redissonClient;

    /**
     * 构造函数，注入Redisson客户端
     *
     * @param redissonClient Redisson客户端实例，不能为null
     */
    public RedissonDistributedLock(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    /**
     * 尝试获取分布式锁
     *
     * <p>该方法会尝试获取指定key的锁，如果锁已被其他线程持有，会等待指定时间；
     * 获取成功后，锁会在指定时间后自动释放，防止死锁。</p>
     *
     * @param lockKey  锁的key，用于标识不同的锁资源
     * @param waitTime  最大等待时间，单位由unit参数指定
     * @param leaseTime 锁的持有时间，超过此时间锁会自动释放.-1:启用看门狗，默认持有30s
     * @param unit      时间单位，不能为null
     * @return true-获取锁成功，false-获取锁失败
     * @throws IllegalArgumentException 如果参数不合法（如lockKey为null或空字符串，时间参数为负值等）
     * @throws DistributedLockException 当线程在等待锁时被中断时抛出
     * @see RLock#tryLock(long, long, TimeUnit)
     */
    @Override
    public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) {
        // 参数校验，确保输入参数的合法性
        validateLockParameters(lockKey, waitTime, leaseTime, unit);

        // 构建完整的锁key，添加统一前缀
        String fullLockKey = buildFullLockKey(lockKey);

        // 获取Redisson锁对象
        RLock lock = redissonClient.getLock(fullLockKey);

        try {
            // 尝试获取锁，等待指定时间，获取成功后设置自动释放时间
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            // 恢复线程中断状态，并抛出业务异常
            Thread.currentThread().interrupt();
            throw new DistributedLockException("Thread interrupted while trying to acquire lock", e);
        }
    }

    /**
     * 阻塞式获取分布式锁
     *
     * <p>该方法会阻塞当前线程，直到成功获取锁为止。获取成功后，锁会在指定时间后自动释放。</p>
     *
     * @param lockKey  锁的key，用于标识不同的锁资源
     * @param leaseTime 锁的持有时间，超过此时间锁会自动释放
     * @param unit      时间单位，不能为null
     * @throws IllegalArgumentException 如果参数不合法
     * @see RLock#lock(long, TimeUnit)
     */
    @Override
    public void lock(String lockKey, long leaseTime, TimeUnit unit) {
        // 参数校验，waitTime设为0表示不等待（实际为阻塞等待）
        validateLockParameters(lockKey, 0, leaseTime, unit);

        // 构建完整的锁key
        String fullLockKey = buildFullLockKey(lockKey);

        // 获取Redisson锁对象
        RLock lock = redissonClient.getLock(fullLockKey);

        // 阻塞式获取锁，无限等待直到成功
        lock.lock(leaseTime, unit);
    }

    /**
     * 释放分布式锁
     *
     * <p>释放当前线程持有的锁。只有锁的持有者才能成功释放锁，非持有者调用此方法无效。</p>
     *
     * @param lockKey 要释放的锁的key，如果为null则直接返回不执行任何操作
     * @see RLock#unlock()
     */
    @Override
    public void unlock(String lockKey) {
        // 空值检查，避免不必要的操作
        if (lockKey == null) return;

        // 构建完整的锁key
        String fullLockKey = buildFullLockKey(lockKey);

        // 获取Redisson锁对象
        RLock lock = redissonClient.getLock(fullLockKey);

        // 只有当前线程持有锁时才执行释放操作，防止误释放其他线程的锁
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }

    /**
     * 检查锁是否被当前线程持有
     *
     * @param lockKey 要检查的锁的key
     * @return true-锁存在且被当前线程持有，false-锁不存在或不被当前线程持有
     * @throws IllegalArgumentException 如果lockKey为null或空字符串
     * @see RLock#isHeldByCurrentThread()
     */
    @Override
    public boolean isHeldByCurrentThread(String lockKey) {
        // 构建完整的锁key
        String fullLockKey = buildFullLockKey(lockKey);

        // 获取Redisson锁对象
        RLock lock = redissonClient.getLock(fullLockKey);

        // 检查当前线程是否持有该锁
        return lock.isHeldByCurrentThread();
    }
}